# -*- coding: utf-8 -*-
"""
高级API调用分析脚本优化版
主要改进：
1. 完善的错误处理机制
2. 配置化API参数
3. 增强的日志系统
4. 性能优化
"""

import json
import logging
import os
import sys
import time
from datetime import datetime
from typing import Tuple, Optional

import pandas as pd
import requests
from tqdm import tqdm
import re
import json

# ------------------ 全局配置 ------------------
class Config:
    # API配置
    CHATGLM_API_URL = "http://10.119.130.188:6007"
    MAX_RETRIES = 3
    RETRY_DELAY = 1.5  # 秒

    # 数据处理
    SAMPLE_FRACTION = 0.1  # 采样比例
    OUTPUT_DIR = "analysis_results"

    # 日志配置
    LOG_FILE = "api_analysis.log"


# ------------------ 日志初始化 ------------------
def setup_logging():
    """配置分级日志系统"""
    logging.basicConfig(
        level=logging.INFO,
        format='%(asctime)s - %(levelname)s - %(module)s - %(message)s',
        handlers=[
            logging.FileHandler(Config.LOG_FILE),
            logging.StreamHandler()
        ]
    )
    logging.getLogger("urllib3").setLevel(logging.WARNING)


# ------------------ API服务模块 ------------------
class ChatGLMClient:
    @staticmethod
    def call_api(prompt: str) -> Optional[str]:
        """
        带重试机制的API调用方法
        :param prompt: 输入的提示文本
        :return: API响应内容或None
        """
        for attempt in range(Config.MAX_RETRIES):
            try:
                response = requests.post(
                    Config.CHATGLM_API_URL,
                    json={"prompt": prompt},
                    timeout=15
                )

                if response.status_code == 200:
                    return response.json().get('response')

                logging.warning(f"API异常响应: {response.status_code} - {response.text}")
                if response.status_code >= 500:
                    time.sleep(Config.RETRY_DELAY * (attempt + 1))

            except requests.exceptions.RequestException as e:
                logging.error(f"请求失败: {str(e)}")
                time.sleep(Config.RETRY_DELAY * (attempt + 1))

            # 动态调整prompt长度
            prompt = ChatGLMClient._adjust_prompt_length(prompt, attempt)

        return None

    @staticmethod
    def _adjust_prompt_length(prompt: str, attempt: int) -> str:
        """智能调整prompt长度"""
        cutoff_map = {0: 4700, 1: 3600, 2: 2200}
        cutoff = cutoff_map.get(attempt, 2000)

        if len(prompt) > cutoff:
            adjusted = prompt[:cutoff]
            last_period = adjusted.rfind('。')
            return adjusted[:last_period + 1] if last_period != -1 else adjusted
        return prompt


# ------------------ 数据处理模块 ------------------
class DataProcessor:
    @staticmethod
    def safe_json_parse(response: str) -> dict:
        """安全解析可能包含不规范空格和中文引号的JSON字符串"""
        # 第一步：基础清理
        cleaned = response.replace('```json', '').replace('```', '').strip()

        # 第二步：替换中文引号（如果有）
        cleaned = cleaned.replace('“', '"').replace('”', '"')

        # 第三步：修正键名前的空格（处理 " key" → "key"）
        cleaned = re.sub(
            r'"\s*([^"]+?)\s*"(?=\s*:)',  # 仅匹配 JSON 键名，确保后面有 `:`
            lambda m: f'"{m.group(1).strip()}"',
            cleaned
        )

        # 第四步：修正键值对中的空格（处理 "key" : → "key":）
        cleaned = re.sub(r'"\s*:\s*', '":', cleaned)

        # 第五步：去除结构符号周围的多余空格（处理 { "key": value } → {"key":value}）
        cleaned = re.sub(r'\s*([\{\},])\s*', r'\1', cleaned)

        # **第六步：去除 JSON 末尾多余的逗号**
        cleaned = re.sub(r',(\s*[}\]])', r'\1', cleaned)  # 只删除 `}` 或 `]` 前面的逗号

        try:
            return json.loads(cleaned)
        except json.JSONDecodeError as e:
            print(f"JSON解析失败: {str(e)}")
            print(f"原始内容: {response}")
            print(f"处理后内容: {cleaned}")
            raise

    @staticmethod
    def parse_api_response(response: str) -> Tuple[Optional[str], Optional[str], Optional[str]]:
        """
        解析API响应内容
        :param response: API返回的原始文本
        :return: (target, stance, opinion)
        """
        if not response:
            return 0, 0, 0

        try:
            # 清理无效字符
            # cleaned = response.replace('```json', '').replace('```', '').strip()
            data = DataProcessor.safe_json_parse(response)

            #print(data)
            # 验证数据结构
            if all(key in data for key in ['target', 'stance', 'opinion']):
                return (
                    str(data['target']).strip(),
                    str(data['stance']).strip().lower(),
                    str(data['opinion']).strip()
                )
            logging.error("响应缺少必要字段\n\n\n\n\n")
            return 0, 0, 0

        except json.JSONDecodeError:
            logging.error("JSON解析失败")
            return 0, 0, 0
        except Exception as e:
            logging.error(f"解析异常: {str(e)}")
            return 0, 0, 0

    @staticmethod
    def validate_stance(stance: str) -> bool:
        """验证立场有效性"""
        return stance in {'支持', '反对', '中立'}


# ------------------ 核心业务逻辑 ------------------
def analyze_comment(background: str, content: str) -> Tuple[Optional[str], Optional[str], Optional[str]]:
    """
    执行单条评论分析
    :param background: 事件背景
    :param content: 评论内容
    :return: (target, stance, opinion)
    """
    instruction = f"""
    背景：“{background}”。请分析以下评论的立场：
    评论内容：“{content}”

    要求：
    构建立场检测三元组需满足以下规范：
    【目标识别准则】
    1. 提取评论直接指涉的最小语义单元实体
    2. 保留字面指称，不做语义引申

    【观点提取规范】
    1. 剥离情感修饰词（如"哈哈"、"！"）
    2. 表达对目标的具体立场观点是什么

    【极性判定标准】
    基于目标-观点对判断：
    1. 支持：认可目标
    2. 反对：否定目标  
    3. 中立：仅陈述事实无倾向
    
    【输出格式】
    必须输出JSON格式：{{"target": "...", "stance": "...", "opinion": "..."}}
    """

    try:
        response = ChatGLMClient.call_api(instruction)
        # print(response+"\n\n\n\n")
        if not response:
            logging.error("API调用失败")
            return 0, 0, 0

        target, stance, opinion = DataProcessor.parse_api_response(response)

        # 二次验证
        if stance and not DataProcessor.validate_stance(stance):
            logging.warning(f"无效立场: {stance}")
            return 0, 0, 0

        return target, stance, opinion

    except Exception as e:
        logging.error(f"分析异常: {str(e)}")
        return 0, 0, 0


def process_data(input_path: str, sample: bool = False) -> str:
    """
    处理整个数据集
    :param input_path: 输入文件路径
    :param sample: 是否采样
    :return: 输出文件路径
    """
    # 读取数据
    try:
        df = pd.read_excel(input_path)
        logging.info(f"成功读取数据: {len(df)}条记录")
    except Exception as e:
        logging.error(f"文件读取失败: {str(e)}")
        sys.exit(1)

    # 数据采样
    if sample:
        df = df.sample(frac=Config.SAMPLE_FRACTION, random_state=42)
        logging.info(f"采样后数据量: {len(df)}条")

    # 初始化列
    result_cols = ['pred_target', 'pred_stance', 'pred_opinion']
    df[result_cols] = None

    # 处理数据
    for idx, row in tqdm(df.iterrows(), total=len(df), desc="分析进度"):
        try:
            target, stance, opinion = analyze_comment(row['background'], row['content'])
            # print(target + " " + stance + " " + "opinion")
            df.loc[idx, result_cols] = [target, stance, opinion]
        except Exception as e:
            logging.error(f"处理第{idx}行失败: {str(e)}")

    # 保存结果
    os.makedirs(Config.OUTPUT_DIR, exist_ok=True)
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    output_path = os.path.join(
        Config.OUTPUT_DIR,
        f"result_{os.path.basename(input_path).split('.')[0]}_{timestamp}.xlsx"
    )

    try:
        df.to_excel(output_path, index=False)
        logging.info(f"结果已保存至: {output_path}")
        return output_path
    except Exception as e:
        logging.error(f"文件保存失败: {str(e)}")
        sys.exit(1)


# ------------------ 主程序 ------------------
if __name__ == '__main__':
    setup_logging()

    try:
        # 示例文件路径
        input_file = "./test_data/output.xlsx"

        # 执行处理
        logging.info("启动分析流程")
        result_path = process_data(input_file, sample=True)
        logging.info(f"分析完成，文件保存在: {result_path}")

    except KeyboardInterrupt:
        logging.warning("用户中断操作")
        sys.exit(130)
    except Exception as e:
        logging.critical(f"主程序错误: {str(e)}")
        sys.exit(1)